package com.ffcs.crmd.tsp.server.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.Trigger.TriggerState;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ffcs.crmd.tsp.common.constant.LoggerName;
import com.ffcs.crmd.tsp.common.enu.QuartzResultType;
import com.ffcs.crmd.tsp.common.exception.TspException;
import com.ffcs.crmd.tsp.common.server.ScheduleResult;
import com.ffcs.crmd.tsp.core.util.MixAll;
import com.ffcs.crmd.tsp.server.api.Handler;
import com.ffcs.crmd.tsp.server.jdbc.entity.Job;
import com.ffcs.crmd.tsp.server.util.JobDetailHelper;

/**
 * 
 * 功能说明:quartz调度处理中心
 *
 * @author ZHONGFUHUA
 * 
 * @since 1.0.0-SNAPSHOT
 *
 */
public class QuartzSchedulerHandler implements Handler {
    
    /**
     * 日志
     */
    private final static Logger LOG = LoggerFactory.getLogger(LoggerName.ServerLoggerName);
    
    /**
     * 调度执行器
     */
    private Scheduler           scheduler;
    
    /**
     * 调度执行器工厂
     */
    private StdSchedulerFactory schedulerFactory;
    
    /**
     * 集群名称
     */
    private static String       clusterName;
    
    /**
     * 集群实例
     */
    private static String       instanceId;
    
    public QuartzSchedulerHandler(Properties properties) throws SchedulerException {
        this.schedulerFactory = new StdSchedulerFactory(properties);
        this.scheduler = schedulerFactory.getScheduler();
        clusterName = this.scheduler.getSchedulerName();
        instanceId = this.scheduler.getSchedulerInstanceId();
    }
    
    public ScheduleResult addJobDetail(com.ffcs.crmd.tsp.server.jdbc.entity.JobDetail jobDetail, Job job) {
        ScheduleResult result = new ScheduleResult();
        try {
            JobDetail quartzJobDetail = JobDetailHelper.convert2JobDetail(jobDetail, job);
            Trigger trigger = null;
            switch (job.getRepeatType()) {
                case 0:
                    trigger = JobDetailHelper.convert2SimpleTrigger(jobDetail, job, false);
                    break;
                case 1:
                    trigger = JobDetailHelper.convert2CronTrigger(jobDetail, job, false);
                    break;
            }
            
            boolean exist = scheduler.checkExists(JobKey.jobKey(jobDetail.getName(), job.getName()));
            
            if (!exist) {
                scheduler.scheduleJob(quartzJobDetail, trigger);
                if (jobDetail.getRunning() == 0) {
                    scheduler.pauseJob(quartzJobDetail.getKey());
                    scheduler.pauseTrigger(trigger.getKey());
                }
                LOG.debug("SERVER-QUARTZ: add a job detail[{}] success.", quartzJobDetail.getKey());
            } else {
                LOG.debug("SERVER-QUARTZ: add a job detail but it's exist[{}] success.", quartzJobDetail.getKey());
            }
        } catch (Exception e) {
            result.setResultType(QuartzResultType.FAIL);
            result.setErrorMessage(e.getMessage());
            LOG.error("SERVER-QUARTZ: add a job detail[{}.{}] exception:", job.getName(), jobDetail.getName(), e);
        }
        return result;
    }
    
    public ScheduleResult deleteJobDetail(com.ffcs.crmd.tsp.server.jdbc.entity.JobDetail jobDetail) {
        ScheduleResult result = new ScheduleResult();
        try {
            if (StringUtils.isBlank(jobDetail.getJobName()) || StringUtils.isBlank(jobDetail.getName())) {
                throw new TspException("job name and job detail name can't be null at the same time");
            }
            scheduler.deleteJob(JobKey.jobKey(jobDetail.getName(), jobDetail.getJobName()));
            LOG.info("SERVER-QUARTZ: delete a job detail[{}.{}] success.", jobDetail.getJobName(), jobDetail.getName());
        } catch (Exception e) {
            result.setResultType(QuartzResultType.FAIL);
            result.setErrorMessage(e.getMessage());
            LOG.error("SERVER-QUARTZ: delete a job detail[{}.{}] exception:", jobDetail.getJobName(), jobDetail.getName(), e);
        }
        return result;
    }
    
    public ScheduleResult deleteJobDetail(List<com.ffcs.crmd.tsp.server.jdbc.entity.JobDetail> jobDetailList) {
        ScheduleResult result = new ScheduleResult();
        try {
            List<JobKey> jobKeyList = new ArrayList<JobKey>();
            if (CollectionUtils.isNotEmpty(jobDetailList)) {
                for (com.ffcs.crmd.tsp.server.jdbc.entity.JobDetail jobDetail : jobDetailList) {
                    if (StringUtils.isBlank(jobDetail.getJobName()) || StringUtils.isBlank(jobDetail.getName())) {
                        throw new TspException("job name and job detail name can't be null at the same time");
                    }
                    jobKeyList.add(JobKey.jobKey(jobDetail.getName(), jobDetail.getJobName()));
                    LOG.info("SERVER-QUARTZ: add a job detail[{}.{}] to be deleted.", jobDetail.getName(), jobDetail.getJobName());
                }
                scheduler.deleteJobs(jobKeyList);
                LOG.info("SERVER-QUARTZ: delete job detail list[{}] success.", jobKeyList);
            }
        } catch (Exception e) {
            result.setResultType(QuartzResultType.FAIL);
            result.setErrorMessage(e.getMessage());
            LOG.error("SERVER-QUARTZ: delete job detail list[{}] exception:", jobDetailList, e);
        }
        return result;
    }
    
    public com.ffcs.crmd.tsp.server.jdbc.entity.JobDetail getJobDetail(String jobName, String jobDetailName) {
        try {
            com.ffcs.crmd.tsp.server.jdbc.entity.JobDetail jobDetail = new com.ffcs.crmd.tsp.server.jdbc.entity.JobDetail();
            if (scheduler.checkExists(JobKey.jobKey(jobDetailName, jobName))) {
                Trigger trigger = scheduler
                    .getTrigger(TriggerKey.triggerKey(MixAll.TSP_TRIGGER_PREFIX + jobDetailName, MixAll.TSP_TRIGGER_PREFIX + jobName));
                JobDetail quartzJobDetail = scheduler.getJobDetail(JobKey.jobKey(jobDetailName, jobName));
                TriggerState triggerState = scheduler
                    .getTriggerState(TriggerKey.triggerKey(MixAll.TSP_TRIGGER_PREFIX + jobDetailName, MixAll.TSP_TRIGGER_PREFIX + jobName));
                jobDetail.setName(jobDetailName);
                jobDetail.setDescription(quartzJobDetail.getDescription());
                if (trigger != null) {
                    jobDetail.setNextTime(trigger.getNextFireTime());
                    jobDetail.setPrevTime(trigger.getPreviousFireTime());
                    jobDetail.setStartTime(trigger.getStartTime());
                    jobDetail.setEndTime(trigger.getEndTime());
                }
                switch (triggerState) {
                    case PAUSED:
                        jobDetail.setRunning((short) 0);
                        break;
                    case NORMAL:
                        jobDetail.setRunning((short) 1);
                        break;
                    case BLOCKED:
                        jobDetail.setRunning((short) 2);
                        break;
                    case COMPLETE:
                        jobDetail.setRunning((short) 3);
                        break;
                    case ERROR:
                        jobDetail.setRunning((short) 4);
                        break;
                    default:
                        jobDetail.setRunning((short) 5);
                        break;
                }
                return jobDetail;
            } else {
                LOG.warn("SERVER-QUARTZ: get a job detail[{}.{}] is't exist.", jobName, jobDetailName);
            }
        } catch (SchedulerException e) {
            LOG.error("SERVER-QUARTZ: get a job detail exception:", e);
        }
        return null;
    }
    
    
    public ScheduleResult pauseJob(Job job) {
        ScheduleResult result = new ScheduleResult();
        try {
            scheduler.pauseJobs(GroupMatcher.jobGroupEquals(job.getName()));
            scheduler.pauseTriggers(GroupMatcher.triggerGroupEquals(job.getName()));
            LOG.info("SERVER-QUARTZ: pause a job[{}] success.", job.getName());
        } catch (Exception e) {
            result.setResultType(QuartzResultType.FAIL);
            result.setErrorMessage(e.getMessage());
            LOG.error("SERVER-QUARTZ: pause a job[{}] exception:", job.getName(), e);
        }
        return result;
    }
    
    public ScheduleResult pauseJobDetail(com.ffcs.crmd.tsp.server.jdbc.entity.JobDetail jobDetail) {
        ScheduleResult result = new ScheduleResult();
        try {
            if (StringUtils.isBlank(jobDetail.getJobName()) || StringUtils.isBlank(jobDetail.getName())) {
                throw new TspException("job name or job detail name can't be null at the same time");
            }
            scheduler.pauseJob(JobKey.jobKey(jobDetail.getName(), jobDetail.getJobName()));
            scheduler.pauseTrigger(
                TriggerKey.triggerKey(MixAll.TSP_TRIGGER_PREFIX + jobDetail.getName(), MixAll.TSP_TRIGGER_PREFIX + jobDetail.getJobName()));
            LOG.info("SERVER-QUARTZ: pause a job detail[{}.{}] success.", jobDetail.getJobName(), jobDetail.getName());
        } catch (Exception e) {
            result.setResultType(QuartzResultType.FAIL);
            result.setErrorMessage(e.getMessage());
            LOG.error("SERVER-QUARTZ: pause a job detail[{}.{}] exception:", jobDetail.getJobName(), jobDetail.getName(), e);
        }
        return result;
    }
    
    public ScheduleResult resumeJob(Job job) {
        ScheduleResult result = new ScheduleResult();
        try {
            scheduler.resumeJobs(GroupMatcher.jobGroupEquals(job.getName()));
            scheduler.resumeTriggers(GroupMatcher.triggerGroupEquals(job.getName()));
            LOG.info("SERVER-QUARTZ: resume a job[{}] success.", job.getName());
        } catch (Exception e) {
            result.setResultType(QuartzResultType.FAIL);
            result.setErrorMessage(e.getMessage());
            LOG.error("SERVER-QUARTZ: resume a job[{}] exception:", job.getName(), e);
        }
        return result;
    }
    
    public ScheduleResult resumeJobDetail(com.ffcs.crmd.tsp.server.jdbc.entity.JobDetail jobDetail) {
        ScheduleResult result = new ScheduleResult();
        try {
            if (StringUtils.isBlank(jobDetail.getJobName()) || StringUtils.isBlank(jobDetail.getName())) {
                throw new TspException("job name or job detail name can't be null at the same time");
            }
            scheduler.resumeJob(JobKey.jobKey(jobDetail.getName(), jobDetail.getJobName()));
            scheduler.resumeTrigger(
                TriggerKey.triggerKey(MixAll.TSP_TRIGGER_PREFIX + jobDetail.getName(), MixAll.TSP_TRIGGER_PREFIX + jobDetail.getJobName()));
            LOG.info("SERVER-QUARTZ: resume a job detail[{}.{}] success.", jobDetail.getJobName(), jobDetail.getName());
        } catch (Exception e) {
            result.setResultType(QuartzResultType.FAIL);
            result.setErrorMessage(e.getMessage());
            LOG.error("SERVER-QUARTZ: resume a job detail[{}.{}] exception:", jobDetail.getJobName(), jobDetail.getName(), e);
        }
        return result;
    }
    
    @Override
    public void shutdown() throws TspException {
        try {
            if (scheduler != null && scheduler.isStarted() && !scheduler.isShutdown()) {
                scheduler.shutdown();
                LOG.info("SERVER-QUARTZ: Quartz handler clusterName[{}] instanceId[{}] shutdown ok.", scheduler.getSchedulerName(),
                    scheduler.getSchedulerInstanceId());
            }
        } catch (SchedulerException e) {
            throw new TspException(e);
        }
    }
    
    @Override
    public void start() throws TspException {
        try {
            scheduler.start();
            System.out.println(String.format("tsp server cluster name [%s]", scheduler.getSchedulerName()));
            LOG.info("SERVER-QUARTZ: Quartz handler clusterName[{}] instanceId[{}] start ok.", scheduler.getSchedulerName(),
                scheduler.getSchedulerInstanceId());
        } catch (SchedulerException e) {
            throw new TspException(e);
        }
    }
    
    public ScheduleResult updateJobDetail(com.ffcs.crmd.tsp.server.jdbc.entity.JobDetail jobDetail, Job job) {
        ScheduleResult result = new ScheduleResult();
        try {
            JobDetail quartzJobDetail = JobDetailHelper.convert2JobDetail(jobDetail, job);
            Trigger trigger = null;
            switch (job.getRepeatType()) {
                case 0:
                    trigger = JobDetailHelper.convert2SimpleTrigger(jobDetail, job, true);
                    break;
                case 1:
                    trigger = JobDetailHelper.convert2CronTrigger(jobDetail, job, true);
                    break;
            }
            Set<Trigger> triggerSet = new HashSet<Trigger>();
            triggerSet.add(trigger);
            scheduler.scheduleJob(quartzJobDetail, triggerSet, true);
            if (jobDetail.getRunning() == 0) {
                scheduler.pauseJob(quartzJobDetail.getKey());
                scheduler.pauseTrigger(trigger.getKey());
            }
            LOG.info("SERVER-QUARTZ: update a job detail[{}] success.", quartzJobDetail.getKey());
        } catch (Exception e) {
            result.setResultType(QuartzResultType.FAIL);
            result.setErrorMessage(e.getMessage());
            LOG.error("SERVER-QUARTZ: update a job detail[{}.{}] exception:", job.getName(), jobDetail.getName(), e);
        }
        return result;
    }
    
    public ScheduleResult updateJobDetail(List<com.ffcs.crmd.tsp.server.jdbc.entity.JobDetail> jobDetailList, Job job) {
        ScheduleResult result = new ScheduleResult();
        try {
            if(CollectionUtils.isEmpty(jobDetailList) || job == null){
                return result;
            }
            Map<JobDetail, Set<? extends Trigger>> jobDetailMaps = new HashMap<JobDetail, Set<? extends Trigger>>();
            for (com.ffcs.crmd.tsp.server.jdbc.entity.JobDetail jobDetail : jobDetailList) {
                JobDetail quartzJobDetail = JobDetailHelper.convert2JobDetail(jobDetail, job);
                Trigger trigger = null;
                switch (job.getRepeatType()) {
                    case 0:
                        trigger = JobDetailHelper.convert2SimpleTrigger(jobDetail, job, true);
                        break;
                    case 1:
                        trigger = JobDetailHelper.convert2CronTrigger(jobDetail, job, true);
                        break;
                }
                Set<Trigger> triggerSet = new HashSet<Trigger>();
                triggerSet.add(trigger);
                jobDetailMaps.put(quartzJobDetail, triggerSet);
                LOG.info("SERVER-QUARTZ: update a job detail[{}] success.", quartzJobDetail.getKey());
            }
            scheduler.scheduleJobs(jobDetailMaps, true);
            for (com.ffcs.crmd.tsp.server.jdbc.entity.JobDetail jobDetail : jobDetailList) {
                if (jobDetail.getRunning() == 0) {
                    scheduler.pauseJob(JobKey.jobKey(jobDetail.getName(), jobDetail.getJobName()));
                    scheduler.pauseTrigger(TriggerKey.triggerKey(MixAll.TSP_TRIGGER_PREFIX + jobDetail.getName(),
                        MixAll.TSP_TRIGGER_PREFIX + jobDetail.getJobName()));
                }
            }
        } catch (Exception e) {
            result.setResultType(QuartzResultType.FAIL);
            result.setErrorMessage(e.getMessage());
            LOG.error("SERVER-QUARTZ: update a job[{}] exception:", job.getName(), e);
        }
        return result;
    }
    
    /**
     * 功能说明: 获取clusterName
     *
     * @return clusterName clusterName
     */
    public static String getClusterName() {
        return clusterName;
    }
    
    /**
     * 功能说明: 获取instanceId
     *
     * @return instanceId instanceId
     */
    public static String getInstanceId() {
        return instanceId;
    }
    
}
